/**
 * @file node.c
 * Implementation of the <b>node<b/> module.
 */

#define _GNU_SOURCE // accept4

#include <stdlib.h>
#include <assert.h>

#include <sys/select.h>
#include <unistd.h>

#include "node.h"
#include "common.h"
#include "net.h"
#include "proto.h"
#include "ui.h"

void node_init(Node *node, const Addr *join)
{
	/* The node data structure is initialized before the call to node_init */

	/* Create listening sockets */
	/*	sock_ui_listen */
	if((node->sock_ui_listen = socket_create(node, SOCK_STREAM, node->port_ui)) == -1) {
		verbose(node, VERROR, "Creation of UI listening port failed.\n");
		node_shutdown(node, 1);
	}
	verbose(node, VSYSCL, "listen(sock_ui_listen)\n");
	if(listen(node->sock_ui_listen, 1) == -1) {
		perror("listen(sock_ui_listen)");
		node_shutdown(node, 1);
	}
	/*	sock_tcp_listen */
	if((node->sock_tcp_listen = socket_create(node, SOCK_STREAM, addr_get_tcp_port(node->addr))) == -1) {
		verbose(node, VERROR, "Creation of TCP listening port failed.\n");
		node_shutdown(node, 1);
	}
	verbose(node, VSYSCL, "listen(sock_tcp_listen)\n");
	if(listen(node->sock_tcp_listen, 1) == -1) {
		perror("listen(sock_tcp_listen)");
		node_shutdown(node, 1);
	}
	/*	sock_udp_listen */
	if((node->sock_udp = socket_create(node, SOCK_DGRAM, addr_get_udp_port(node->addr))) == -1) {
		verbose(node, VERROR, "Creation of UDP listening port failed.\n");
		node_shutdown(node, 1);
	}
	node->sock_ui_conn = 0;
	node->sock_tcp_conn = 0;
	if(join)
		proto_join(node, join);
}

void node_loop(Node *node)
{
	while(1) {
		/* Set up arguments for select() */
		fd_set readfds;
		int ready = 0;
		int s1 = node->sock_ui_conn? node->sock_ui_conn : node->sock_ui_listen;
		int s2 = node->sock_tcp_conn? node->sock_tcp_conn : node->sock_tcp_listen;
		int s3 = node->sock_udp;
		int nfds = max(max(s1, s2), s3)+1;

		FD_ZERO(&readfds);
		FD_SET(s1, &readfds);
		FD_SET(s2, &readfds);
		FD_SET(s3, &readfds);

		/* select() */
		verbose(node, VSYSCL, "select()\n");
		ready = select(nfds, &readfds, NULL, NULL, NULL);

		/* Treat incomming requests */
		verbose(node, VDEBUG, "select() returns with %d ready file descriptor(s)\n", ready);
		Msg received; // the received P2P message

		if(FD_ISSET(node->sock_ui_listen, &readfds)) {
			verbose(node, VDEBUG, "activity on sock_ui_listen\n");
			verbose(node, VSYSCL, "accept(node->sock_ui_listen)\n");
			/* TODO: close the previous connection */
			if((node->sock_ui_conn = accept4(node->sock_ui_listen, NULL, NULL, SOCK_NONBLOCK)) == -1) {
				perror("accept4");
				node_shutdown(node, 1); // FIXME change this behaviour
			}
		}

		/*	Check for activity from the UI */
		if(FD_ISSET(node->sock_ui_conn, &readfds)) {
			verbose(node, VDEBUG, "activity on sock_ui_conn\n");
			int ret = ui_command(node);
			if(ret == P2P_UI_QUIT) {
				/* Close the connection */
				socket_close(node, node->sock_ui_conn);
				node->sock_ui_conn = 0;
			} else if(ret == P2P_UI_TERMINATE) {
				/* Shutdown the node */
				node_shutdown(node, 0);
			}
		}

		/*	Check for incomming TCP connections */
		if(FD_ISSET(node->sock_tcp_listen, &readfds)) {
			verbose(node, VDEBUG, "activity on sock_tcp_listen\n");
			verbose(node, VSYSCL, "accept(node->sock_tcp_listen)\n");
			if((node->sock_tcp_conn = accept4(node->sock_tcp_listen, NULL, NULL, SOCK_NONBLOCK)) == -1) {
				perror("accept4");
				node_shutdown(node, 1); // FIXME change this behaviour
			}
		}

		/*	Check for activity on already existing TCP connexion */
		if(FD_ISSET(node->sock_tcp_conn, &readfds)) {
			verbose(node, VDEBUG, "activity on sock_tcp_conn\n");
			/* Read the message */
			if(msg_read_tcp(node, &received, node->sock_tcp_conn) == P2P_OK)
			/* Deal with the message */
				proto_recv(node, &received);
			/* Close connection */
			socket_close(node, node->sock_tcp_conn);
			node->sock_tcp_conn = 0;
		}

		/*	Check for activity on UDP port */
		if(FD_ISSET(node->sock_udp, &readfds)) {
			verbose(node, VDEBUG, "activity on sock_udp\n");
			/* Read the message */
			if(msg_read_udp(node, &received, node->sock_udp) == P2P_OK)
			/* Deal with the message */
				proto_recv(node, &received);
		}
	}
}

void node_shutdown(Node *node, int status)
{
	proto_leave(node);
	free(node->srv_name);
	free(node->dir_name);
	free(node->addr);
	free(node->addr_left);
	free(node->addr_right);
	search_delete(&node->search);
	free(node);
	exit(status);
}
